﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using ThreeByte.ActiveDeck.Config;
using ThreeByte.ActiveDeck.Data;
using ThreeByte.ActiveDeck.Data.UserResponse;
using log4net;
using System.Web;
using System.ComponentModel;
using System.Reflection;
using ThreeByte.Logging;
using System.IO.Compression;

namespace ThreeByte.ActiveDeck.Service
{
    public class WebServiceClient : INotifyPropertyChanged
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(WebServiceClient));
        private static readonly XNamespace ns = ServiceConfiguration.NAMESPACE;

        #region Public Properties
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string info) {
            if(PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private bool _hasError;
        public bool HasError {
            get {
                return _hasError;
            }
            private set {
                _hasError = value;
                NotifyPropertyChanged("HasError");
            }
        }

        private string _error;
        public string Error {
            get {
                return _error;
            }
            private set {
                _error = value;
                NotifyPropertyChanged("Error");
                HasError = (_error != null);
            }
        }
        #endregion Public Properties


        public NetworkCredential Credential;

        public WebServiceClient(NetworkCredential cred) {
            Credential = cred;
        }

        //Service-Specific methods
        public XElement NewPresentation(string name) {
            log.DebugFormat("Creating New Presentation {0}", name);

            try {
                Uri svcLink = new Uri(string.Format("{0}/NewPresentation/{1}", ServiceConfiguration.SERVICE_ENDPOINT, name));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                //TODO: See if this is an exception response
                Error = null;
                return responseNode;
                
            } catch (Exception ex) {
                log.Error("New Presentation Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public XElement RenamePresentation(string presentationId, string name) {
            log.DebugFormat("Rename Presentation [{0}]: {1}", presentationId, name);

            try {
                Uri svcLink = new Uri(string.Format("{0}/RenamePresentation/{1}/{2}", ServiceConfiguration.SERVICE_ENDPOINT, presentationId, name));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                //TODO: See if this is an exception response
                Error = null;
                return responseNode;

            } catch (Exception ex) {
                log.Error("RenamePresentation Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public bool SetPresentationState(string presentation, int section) {
            log.DebugFormat("Set Presentation State [{0}]: {1}", presentation, section);

            try {
                Uri svcLink = new Uri(string.Format("{0}/SetCurrentState/{1}/{2}", ServiceConfiguration.SERVICE_ENDPOINT, presentation, section));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                ParseResponse(responseNode);
                Error = null;
                return true;
            } catch(Exception ex) {
                Error = "Cannot update Presentation State";
                log.Error("SetPresentationState Error", ex);
                return false;
            }
        }

        public PresentationHeader RecordPresentationStart(string presentation) {
            log.DebugFormat("RecordPresentationStart [{0}]", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/StartPresentation/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                Error = null;
                return new PresentationHeader(responseNode);
            } catch(Exception ex) {
                Error = "Cannot Record Presentation Start";
                log.Error("RecordPresentationStart Error", ex);
                return null;
            }
        }

        public bool RecordPresentationEnd(string presentation) {
            log.DebugFormat("RecordPresentationEnd [{0}]", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/EndPresentation/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                ParseResponse(responseNode);
                Error = null;
                return true;
            } catch(Exception ex) {
                Error = "Cannot Record Presentation End";
                log.Error("RecordPresentationEnd Error", ex);
                return false;
            }
        }

        public XElement GetPresentationState(string presentation) {
            log.DebugFormat("Get Presentation State [{0}]", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/GetCurrentState/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                Error = null;
                return responseNode;
            } catch(Exception ex) {
                Error = "Cannot Get Presentation State";
                log.Error("GetPresentationState Error", ex);
                return null;
            }
        }

        public PresentationHeader GetPresentationInfo(string presentation) {
            log.DebugFormat("Get Presentation Info [{0}]", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/GetPresentationInfo/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;

                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                PresentationHeader presHeader = new PresentationHeader(responseNode);
                Error = null;
                return presHeader;
            } catch(Exception ex) {
                //Error = "Cannot Get Presentation Info";
                log.Error("GetPresentationInfo Error", ex);
                return null;
            }
        }

        public XElement PublishPresentation(string presentationId, XElement presentationConfig){
            log.DebugFormat("Publish Presentation: {0}", presentationId);

            try {
                Uri svcLink = new Uri(string.Format("{0}/Publish/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentationId));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                
                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(presentationConfig.ToString());

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                //TODO: See if this is an exception response
                Error = null;
                return responseNode;
            } catch(Exception ex) {
                Error = "Cannot Publish Presentation";
                log.Error("Publish Presentation Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public XElement PublishPresentationCompressed(string presentationId, XElement presentationConfig) {
            log.DebugFormat("Publish Presentation Compressed: {0}", presentationId);

            try {
                Uri svcLink = new Uri(string.Format("{0}/PublishCompressed/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentationId));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";

                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(presentationConfig.ToString());

                //Compress byte stream
                XElement compressedConfig = new XElement(presentationConfig.Name);
                compressedConfig.Add(new XAttribute("ID", presentationConfig.Attribute("ID").Value));
                compressedConfig.Add(new XAttribute("SlideCount", presentationConfig.Attribute("SlideCount").Value));

                //Decompress to string
                using(MemoryStream outStream = new MemoryStream()) {
                    using(MemoryStream inStream = new MemoryStream(stringArray)) {
                        using(GZipStream zipStream = new GZipStream(outStream, CompressionMode.Compress)) {
                            byte[] buf = new byte[4096];
                            int bytesRead = inStream.Read(buf, 0, buf.Length);
                            while(bytesRead > 0) {
                                zipStream.Write(buf, 0, bytesRead);
                                bytesRead = inStream.Read(buf, 0, buf.Length);
                            }
                        }

                       string compressedString = Convert.ToBase64String(outStream.ToArray());
                       compressedConfig.Value = compressedString;
                    }
                }

                compressedConfig.Add(AuthorizationUtil.ToAuthenticationXML(Credential));

                byte[] compressedArray = Encoding.UTF8.GetBytes(compressedConfig.ToString());

                reqStream.Write(compressedArray, 0, compressedArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
               
                Error = null;
                return responseNode;
            } catch(Exception ex) {
                Error = "Cannot Publish Compressed Presentation";
                log.Error("Publish Presentation Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public bool UploadResource(string presentationId, string resourceId, GraphicResource resource) {
            //TODO: Testing only
            //System.Threading.Thread.Sleep(1500);  //Simulate a longer upload
            log.DebugFormat("Upload Resource: {0}/{1}", presentationId, resourceId);
            try {
                Uri svcLink = new Uri(string.Format("{0}/Resource/{1}/{2}", ServiceConfiguration.SERVICE_ENDPOINT, presentationId, resourceId));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement resourceXml = resource.ToXml();
                resourceXml.Add(AuthorizationUtil.ToAuthenticationXML(Credential));

                byte[] stringArray = Encoding.UTF8.GetBytes(resourceXml.ToString());

                reqStream.Write(stringArray, 0, stringArray.Length);
                
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                ParseResponse(responseNode);
                Error = null;
                return true;
            } catch(Exception ex) {
                log.Error("Upload Resource Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public bool UploadResourceBlock(string presentationId, string resourceId, ByteBlock block) {

            log.DebugFormat("Upload Resource Block: {0}/{1}/{2}", presentationId, resourceId, block.BlockName);
            try {
                Uri svcLink = new Uri(string.Format("{0}/ResourceBlock/{1}/{2}?id={3}&md5={4}", ServiceConfiguration.SERVICE_ENDPOINT,
                                                        presentationId, resourceId, HttpUtility.UrlEncode(block.BlockName), HttpUtility.UrlEncode(block.ContentHash)));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement blockXml = block.ToXml();
                blockXml.Add(AuthorizationUtil.ToAuthenticationXML(Credential));

                byte[] stringArray = Encoding.UTF8.GetBytes(blockXml.ToString());

                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                ParseResponse(responseNode);
                Error = null;
                return true;
            } catch(Exception ex) {
                log.Error("Upload Resource Block Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public bool CommitResource(string presentationId, string resourceId, BlockList blockList, XElement dataHeader) {

            log.DebugFormat("Commit Resource Blocks: {0}/{1}/{2}", presentationId, resourceId, blockList.BlockIDs.Count);
            try {
                Uri svcLink = new Uri(string.Format("{0}/CommitResource/{1}/{2}", ServiceConfiguration.SERVICE_ENDPOINT,
                                                        presentationId, resourceId));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement blockXml = blockList.ToXml();
                blockXml.Add(AuthorizationUtil.ToAuthenticationXML(Credential));
                blockXml.Add(dataHeader);

                byte[] stringArray = Encoding.UTF8.GetBytes(blockXml.ToString());

                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                ParseResponse(responseNode);
                Error = null;
                return true;
            } catch(Exception ex) {
                log.Error("Upload Resource Block Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public XElement GetResourceInfo(string presentationId, string resourceId) {

            log.DebugFormat("Get Resource Info: {0}/{1}", presentationId, resourceId);
            try {
                Uri svcLink = new Uri(string.Format("{0}/GetResourceInfo/{1}/{2}", ServiceConfiguration.SERVICE_ENDPOINT,
                                                        presentationId, resourceId));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                return responseNode;
            } catch(Exception ex) {
                log.Error("Get Resource Info Error", ex);
                throw new Exception("Error getting resource info", ex);
            }
        }

        public List<Guid> GetResourceList(string presentationId, int count = 0, int skip = 0) {

            log.DebugFormat("Get Resource List: {0}/{1}/{2}", presentationId, count, skip);
            try {
                Uri svcLink = new Uri(string.Format("{0}/GetResourceList/{1}/{2}/{3}", ServiceConfiguration.SERVICE_ENDPOINT,
                                                        presentationId, count, skip));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                List<Guid> resources = new List<Guid>();
                //Parse the response node
                foreach(XElement x in responseNode.Elements(ns + "Resource")) {
                    resources.Add(Guid.Parse(x.Attribute("ID").Value));
                }
                return resources;
            } catch(Exception ex) {
                log.Error("Get Resource List Error", ex);
                throw new Exception("Error getting resource list", ex);
            }
        }


        public void DeleteResources(string presentationId, List<Guid> resourceIds) {
            
            log.DebugFormat("Delete Resources: {0} [{1}]", presentationId, resourceIds.Count);
            try {
                Uri svcLink = new Uri(string.Format("{0}/DeleteResources/{1}", ServiceConfiguration.SERVICE_ENDPOINT,
                                                        presentationId));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement resources = new XElement(ns + "Resources");
                foreach(Guid id in resourceIds) {
                    resources.Add(new XElement(ns + "Resource", new XAttribute("ID", id)));
                }
                resources.Add(AuthorizationUtil.ToAuthenticationXML(Credential));

                string data = resources.ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(data);

                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                ParseResponse(responseNode);
            } catch(Exception ex) {
                log.Error("Delete Resources Error", ex);
                throw new Exception("Error deleting resources", ex);
            }

        }

        public static bool SendErrorReport(string report, string presentationId = null, string attachmentPath = null) {
            log.DebugFormat("Send Error report");

            //Get the current version
            Version thisVer = Assembly.GetCallingAssembly().GetName().Version;
            
            try {
                Uri svcLink = new Uri(string.Format("{0}/SubmitErrorReport", ServiceConfiguration.SERVICE_ENDPOINT));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                //Construct the Error Report XML
                XElement errorNode = new XElement(ns + "ErrorReport");
                errorNode.Add(new XAttribute("Source", "AddIn"));
                errorNode.Add(new XAttribute("Version", thisVer));
                if(presentationId != null) {
                    errorNode.Add(new XAttribute("Presentation", presentationId));
                }
                report += "\n\n" + LogUtil.GetDiagnosticString() + "\n\n";

                errorNode.Add(new XCData(report));

                if(attachmentPath != null) {
                    //Read the file
                    byte[] logData = File.ReadAllBytes(attachmentPath);

                    if(logData.Length < 1000000) {  //Is 100K sufficient?
                        //Encode it to base64 and then attach as new element
                        string encodedLogString = Convert.ToBase64String(logData, Base64FormattingOptions.InsertLineBreaks);
                        XElement logNode = new XElement(ns + "LogData");
                        logNode.Value = encodedLogString;
                        errorNode.Add(logNode);
                    }
                }

                byte[] stringArray = Encoding.Default.GetBytes(errorNode.ToString());

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();

                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                ParseResponse(responseNode);
                return true;
            } catch(Exception ex) {
                log.Error("Send Error Report", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }


        public static Version GetAddInVersion() {
            log.DebugFormat("Check AddIn Version");

            try {
                Uri svcLink = new Uri(string.Format("{0}/AddInVersion", ServiceConfiguration.SERVICE_ENDPOINT));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;

                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                //Error = null;
                return Version.Parse(responseNode.Attribute("Version").Value);
            } catch(Exception ex) {
                //Error = "Cannot Get AddIn Version";
                log.Error("GetAddInVersion Error", ex);
                return null;
            }
        }

        public Stream DownloadAddIn() {
            log.DebugFormat("Download AddIn");

            try {
                Uri svcLink = new Uri(string.Format("{0}/Download", ServiceConfiguration.SERVICE_ENDPOINT));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                string authData = AuthorizationUtil.ToAuthenticationXML(Credential).ToString();
                byte[] stringArray = Encoding.UTF8.GetBytes(authData);

                reqStream.Write(stringArray, 0, stringArray.Length);
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                return resp.GetResponseStream();
            } catch(Exception ex) {
                Error = "Cannot Download AddIn";
                log.Error("DownloadAddIn Error", ex);
                return null;
            }

        }

        public bool TestLogin() {
            log.DebugFormat("Test Login");

            try {
                Uri svcLink = new Uri(string.Format("{0}/Login", ServiceConfiguration.SERVICE_ENDPOINT));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(AuthorizationUtil.ToAuthenticationXML(Credential).ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());


                //TODO: See if this is an exception response
                bool loginSuccess = ParseAuthenticationResponse(responseNode);
                Error = null;
                return loginSuccess;
            } catch(System.Configuration.ConfigurationException configEx){
                log.Error("Configuration Exception", configEx); 
                throw configEx;
            } catch(Exception ex) {
                Error = "Cannot Verify Login";
                log.Error("Test Login", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }

        }


        private static void ParseResponse(XElement response) {
            try {
                if(response.Name == "Success") {
                    return;
                }

                if(response.Name == "Error") {
                    throw new Exception(string.Format("{0}: {1}", response.Attribute("Domain").Value, response.Attribute("Message").Value));
                }
            } catch(Exception ex) {
                throw new Exception("Cannot Parse Webserver response", ex);
            }
            throw new Exception("Cannot Parse Webserver response");

        }

        private bool ParseAuthenticationResponse(XElement response) {
            try {
                if(response.Name == "Success") {
                    return true;
                }

                if(response.Name == "Error") {
                    return false;
                }
            } catch(Exception ex) {
                throw new Exception("Cannot Parse Webserver response", ex);
            }
            throw new Exception("Cannot Parse Webserver response");
        }

        public XElement GetUserData(string presentation, int section, int page = 0) {
            log.DebugFormat("Get user Data: {0}/{1}/{2}", presentation, section, page);

            try {
                Uri svcLink = new Uri(string.Format("{0}/UserData/{1}/{2}/{3}", ServiceConfiguration.SERVICE_ENDPOINT, presentation, section, (page == 0 ? string.Empty : page.ToString())));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(AuthorizationUtil.ToAuthenticationXML(Credential).ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                Error = null;
                return responseNode;
            } catch(Exception ex) {
                Error = "Cannot Retrieve User Data";
                log.Error("Get User Data Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public XElement GetGroupedUserData(string presentation, int section, int page = 0) {
            log.DebugFormat("Get Grouped user data: {0}/{1}/{2}", presentation, section, page);

            try {
                Uri svcLink = new Uri(string.Format("{0}/GroupedUserData/{1}/{2}/{3}", ServiceConfiguration.SERVICE_ENDPOINT, presentation, section, (page == 0 ? string.Empty : page.ToString())));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(AuthorizationUtil.ToAuthenticationXML(Credential).ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                Error = null;
                return responseNode;
            } catch(Exception ex) {
                Error = "Cannot Retrieve Grouped User Data";
                log.Error("Get Grouped User Data Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public XElement GetGroupUsers(string presentation, int groupID) {
            log.DebugFormat("Get Group Users: {0}/{1}", presentation, groupID);

            try {
                Uri svcLink = new Uri(string.Format("{0}/GetGroupUsers/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement userNode = new XElement(ns + "Group");
                userNode.Add(AuthorizationUtil.ToAuthenticationXML(Credential));
                userNode.Add(new XElement(ns + "Group", new XAttribute("ID", groupID)));

                byte[] stringArray = Encoding.UTF8.GetBytes(userNode.ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                Error = null;
                return responseNode;
            } catch (Exception ex) {
                Error = "Cannot get group users";
                log.Error("Get Group Users Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public XElement GetUserDataReport(string presentation) {
            log.DebugFormat("Get User Data Report: {0}", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/GetUserDataReport/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(AuthorizationUtil.ToAuthenticationXML(Credential).ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);
                
                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                return responseNode;
            } catch(Exception ex) {
                Error = "Cannot get user data report";
                log.Error("Get User Data Report Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public XElement GetMultiQuestionGroupedUserData(string presentation, Dictionary<int, int> pages) {
            log.DebugFormat("Get Multi-Question Grouped User Data: {0}", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/GetMultiQuestionGroupedUserData/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";

                var reqStream = req.GetRequestStream();

                XElement userNode = new XElement(ns + "Pages");
                userNode.Add(AuthorizationUtil.ToAuthenticationXML(Credential));

                foreach(int p in pages.Keys) {
                    userNode.Add(new XElement(ns + "Page", new XAttribute("ID", p), new XAttribute("CorrectAnswer", pages[p])));
                }

                byte[] stringArray = Encoding.UTF8.GetBytes(userNode.ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();

                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());

                Error = null;
                return responseNode;                
            } catch(Exception ex) {
                Error = "Cannot get grouped user data";
                log.Error("Get grouped user data error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public void SetUserGroup(string presentation, List<string> usernames, int groupID) {
            log.DebugFormat("Set user group: {0}/{1}", presentation, groupID);

            try {
                Uri svcLink = new Uri(string.Format("{0}/SetUserGroup/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement userNode = new XElement(ns + "UserGroups");
                userNode.Add(AuthorizationUtil.ToAuthenticationXML(Credential));
                foreach(string u in usernames) {
                    userNode.Add(new XElement(ns + "User", new XAttribute("ID", u), new XAttribute("Group", groupID)));
                }

                byte[] stringArray = Encoding.UTF8.GetBytes(userNode.ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                ParseResponse(responseNode);
            } catch (Exception ex) {
                Error = "Cannot set user group";
                log.Error("Set User Group Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }            
        }

        public void RemoveUsersFromGroup(string presentation, List<string> usernames, int groupID) {
            log.DebugFormat("Remove user from group: {0}/{1}", presentation, groupID);

            try {
                Uri svcLink = new Uri(string.Format("{0}/RemoveUsersFromGroup/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement userNode = new XElement(ns + "UserGroups");
                userNode.Add(AuthorizationUtil.ToAuthenticationXML(Credential));
                foreach(string user in usernames) {
                    userNode.Add(new XElement(ns + "User", new XAttribute("ID", user), new XAttribute("Group", groupID)));
                }

                byte[] stringArray = Encoding.UTF8.GetBytes(userNode.ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                ParseResponse(responseNode);
            } catch(Exception ex) {
                Error = "Cannot remove users from group";
                log.Error("Remove User From Group Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public void ResetUserData(string presentation, int section, int page = 0) {
            log.DebugFormat("ResetUserData: {0}/{1}/{2}", presentation, section, page);

            try {
                Uri svcLink = new Uri(string.Format("{0}/ResetUserData/{1}/{2}/{3}", ServiceConfiguration.SERVICE_ENDPOINT, presentation, section, (page == 0 ? string.Empty : page.ToString())));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(AuthorizationUtil.ToAuthenticationXML(Credential).ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                ParseResponse(responseNode);
                Error = null;
            } catch(Exception ex) {
                Error = "Cannot Reset User Data";
                log.Error("Reset User Data Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }

        public void ClearAllUserData(string presentation) {
            log.DebugFormat("ClearAllUserData: {0}", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/ClearUserData/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                byte[] stringArray = Encoding.UTF8.GetBytes(AuthorizationUtil.ToAuthenticationXML(Credential).ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                ParseResponse(responseNode);
                Error = null;
            } catch(Exception ex) {
                Error = "Cannot Clear All User Data";
                log.Error("Clear All User Data Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }

        }

        public void RemovePresentationGroups(string presentation, List<int> groupsToBeRemoved) {
            log.DebugFormat("RemovePresentationGroups: {0}", presentation);

            try {
                Uri svcLink = new Uri(string.Format("{0}/RemovePresentationGroups/{1}", ServiceConfiguration.SERVICE_ENDPOINT, presentation));

                var req = WebRequest.Create(svcLink) as HttpWebRequest;
                req.Method = "POST";
                req.ContentType = "text/xml";
                var reqStream = req.GetRequestStream();

                XElement groupNode = new XElement(ns + "Groups");
                groupNode.Add(AuthorizationUtil.ToAuthenticationXML(Credential));
                foreach(int groupID in groupsToBeRemoved) {
                    groupNode.Add(new XElement(ns + "Group", new XAttribute("GroupID", groupID)));
                }

                byte[] stringArray = Encoding.UTF8.GetBytes(groupNode.ToString());
                reqStream.Write(stringArray, 0, stringArray.Length);

                reqStream.Close();
                var resp = (HttpWebResponse)req.GetResponse();
                XElement responseNode = XElement.Load(resp.GetResponseStream());
                ParseResponse(responseNode);
                Error = null;
            } catch(Exception ex) {
                Error = "Cannot Remove Presentation Groups";
                log.Error("Remove Presentation Groups Error", ex);
                throw new Exception("Error communicating with Web Service", ex);
            }
        }
    }
}
